(*****************************************************************************
 * Delphi Plug-in Framework (DPF)
 * Copyright (C) 2006-2007 Tao Yuan
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the
 * License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.                                                               }
 *
 * The Initial Developer of the Original Code is Tao Yuan. Portions created by
 * Tao Yuan are Copyright (C) Tao Yuan (taoy.cn att gmail dott com)
 * All rights reserved.
 *****************************************************************************)
 
unit RDStandardPathResolver;

interface

uses
  Rcl,
  RDIdentity, RdClasses,
  RDPluginmanager, RDPluginRegistry, RDPathResolver, RDExceptions;

type
  TStandardPathResolver = class(TRdLogPersistent, IPathResolver)
  private
    urlMap: IStrStrMap;
  protected
    function resolvePath(baseUrl, path: string): string; overload;
  public
    constructor Create; override;
    (**
     * Configures this resolver instance. Usually this method is called from
     * {@link ObjectFactory object factory} implementation.
     * @param config path resolver configuration data
     *)
    procedure configure(config: TExtendedProperties); stdcall;
    (**
     * Returns URL of {@link #registerContext(Identity, URL) registered} plug-in
     * element context. If context for plug-in element with given ID not
     * registered, this method should throw an {@link IllegalArgumentException}.
     * In other words, this method shouldn't return <code>null</code>.
     * @param id plug-in element identifier
     * @return registered context "home" location
     *)
    function getRegisteredConext(id: string): string; stdcall;
    (**
     * @param id plug-in element identifier
     * @return <code>true</code> if context for plug-in element with given ID
     *         registered
     *)
    function isContextRegistered(id: string): Boolean; stdcall;
    function isResourceExists(url: string): Boolean; stdcall;
    (**
     * Registers "home" URL for given plug-in element.
     * @param idt plug-in element
     * @param url "home" URL for a given plug-in element
     *)
    procedure registerContext(idt: IIdentity; url: string); stdcall;
    (**
     * Should resolve given path to URL for a given identity.
     * @param identity plug-in element for which to resolve path
     * @param path path to be resolved
     * @return resolved absolute URL
     *)
    function resolvePath(identity: IIdentity; path: string): string; overload;
        stdcall;
    (**
     * Unregisters plug-in element from this path resolver.
     * @param id plug-in element identifier
     *)
    procedure unregisterContext(id: string); stdcall;
  end;

implementation

uses
  SysUtils, Classes;

constructor TStandardPathResolver.Create;
begin
  inherited Create(True);
  urlMap := TStrStrHashMap.Create;
end;

procedure TStandardPathResolver.configure(config: TExtendedProperties);
begin
// no-op
end;

function TStandardPathResolver.getRegisteredConext(id: string): string;
begin
  Result := urlMap.Items[id];
  if (result = '') then
      raise EIllegalArgument.Create('unknown plug-in or' //$NON-NLS-1$
              + ' plug-in fragment ID - ' + id); //$NON-NLS-1$
end;

function TStandardPathResolver.isContextRegistered(id: string): Boolean;
begin
  Result := urlMap.containsKey(id);
end;

function TStandardPathResolver.isResourceExists(url: string): Boolean;
begin
  Result := Rcl.FileUtils.IsFile(url)
end;

procedure TStandardPathResolver.registerContext(idt: IIdentity; url: string);
var
  oldUrl: string;
begin
  if (not Supports(idt, IPluginDescriptor)) and
          (not Supports(idt, IPluginFragment)) then
      raise EIllegalArgument.Create(
              'unsupported identity class ');

  oldUrl := urlMap.Items[idt.getId];
  urlMap.Items[idt.getId] := url;
  if (oldUrl <> '') then
      log.warn('old context URL ' + oldUrl //$NON-NLS-1$
              + ' has been replaced with new ' + url //$NON-NLS-1$
              + ' for ' + idt.getId //$NON-NLS-1$
              + ' with key ' + idt.getId()) //$NON-NLS-1$
  else
      log.debug('context URL ' + url //$NON-NLS-1$
              + ' registered for ' + idt.getId //$NON-NLS-1$
              + ' with key ' + idt.getId()); //$NON-NLS-1$      
end;

function TStandardPathResolver.resolvePath(identity: IIdentity; path: string):
    string;
var
  baseUrl: string;
  element: IPluginElement;
begin
  if Supports(identity, IPluginDescriptor)
    or Supports(identity, IPluginFragment) then
      baseUrl := getRegisteredConext(identity.getId)
  else if Supports(identity , IPluginElement) then
  begin
      element := IPluginElement(identity);
      if (element.getDeclaringPluginFragment <> nil) then
          baseUrl := getRegisteredConext(
                  element.getDeclaringPluginFragment.getId)
      else
          baseUrl := getRegisteredConext(
                  element.getDeclaringPluginDescriptor.getId);      
  end
  else
      raise EIllegalArgument.Create('unknown identity class!');

  Result := resolvePath(baseUrl, path);
end;

function TStandardPathResolver.resolvePath(baseUrl, path: string): string;
begin
  if (path = '') or (path = '/') or (path = '\') then //$NON-NLS-1$ //$NON-NLS-2$
      Result := baseUrl;

  try
      //return new URL(homeUrl, path);
      // Patch from Sebastian Kopsan
//      File file = IoUtil.url2file(baseUrl);
//      if ((file != null) && file.isFile()) {
//          String fileName =
//              file.getName().toLowerCase(Locale.getDefault());
//          if (fileName.endsWith(".jar") //$NON-NLS-1$
//                  || fileName.endsWith(".zip")) { //$NON-NLS-1$
//              return new URL("jar:file:/" + file.getAbsolutePath() //$NON-NLS-1$
//                      + "!/" + path); //$NON-NLS-1$
//          }
//      }
//      return new URL(baseUrl, path);
    Rcl.Str.Replace(path, '/', Rcl.FileUtils.Separator);
    while Rcl.Str.StartsWith(Path, Rcl.FileUtils.Separator) do
      Path := Rcl.Str.SubString(Path, Length(Rcl.FileUtils.Separator) + 1, Length(Path));
    Result := Rcl.FileUtils.PathCanonicalize(Rcl.FileUtils.IncludeSlash(baseUrl) + path);
  except
    //on mue : EMalformedURL do
    on e: Exception do
    begin
      log.error('can''t create URL in context of ' + baseUrl //$NON-NLS-1$
              + ' and path ' + path); //$NON-NLS-1$
      raise EIllegalArgument.Create('path ' + path //$NON-NLS-1$
              + ' in context of ' + baseUrl //$NON-NLS-1$
              + ' cause creation of malformed URL'); //$NON-NLS-1$
    end;
  end;  
end;

procedure TStandardPathResolver.unregisterContext(id: string);
var
  url: string;
begin
  url := urlMap.remove(id);
  if (url = '') then
      log.warn('no context was registered with key ' + id) //$NON-NLS-1$
  else
      log.debug('context URL ' + url //$NON-NLS-1$
              + ' un-registered for key ' + id); //$NON-NLS-1$
end;

initialization
  RegisterClass(TStandardPathResolver);

end.
